/******************************************************************************
* Copyright (c) 2010-2011, PCMS Lab
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*     * Redistributions of source code must retain the above copyright
*       notice, this list of conditions and the following disclaimer.
*     * Redistributions in binary form must reproduce the above copyright
*       notice, this list of conditions and the following disclaimer in the
*       documentation and/or other materials provided with the distribution.
*     * Neither the name of the <organization> nor the
*       names of its contributors may be used to endorse or promote products
*       derived from this software without specific prior written permission.
* 
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL "PCMS Lab" BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
function AssertionException(message) {
	this.name = "AssertionException";
	this.message = message;
}

function Constraint(expectedDisplay, assertion)  {
	this.actual = null;
	this.expectedDisplay = expectedDisplay;
	this.assertion = assertion;
}

Constraint.prototype = {
	getMessage:function() {
		return "Expected: " + this.expectedDisplay + 
			   " Actual: " + this.actual;
	},
	matches:function(actual) {
		this.actual = actual;
		return this.assertion(this.actual);
	}
}

var Is = {
	EqualTo: function(expected) {
		return new Constraint(expected, function(actual) { return actual==expected; });
	},
	True: function() {
		return Is.EqualTo(true);
	},
	False: function() {
		return Is.EqualTo(false);
	},
	Null: function() {
		return Is.EqualTo(null);
	},
	Empty: function() {
		return new Constraint("[Empty]", function(actual) { return String(actual)==""; });
	},
	Not: {
		EqualTo: function(expected) {
			return new Constraint(expected, function(actual) { return actual!=expected; });
		},
		Null: function() {
			return new Constraint("[not null]", function(actual) { return actual!=null; });
		},
		Number: function() {
			return new Constraint("[NaN]", function(actual) { return isNaN(Number(actual)); });
		},
		Empty: function() {
			return new Constraint("[not empty]", function(actual) { return String(actual)!=""; });
		}
	}
}

var Text = {
	Contains: function(substring) {
		return new Constraint("Contains '" + substring + "'", function(actual) {return actual.indexOf(substring)!=-1;});
	}
}

var Assert = {
	That: function( actual, expression, message ) {
		var constraint = expression;
		if (!constraint.matches(actual)) {
			var msg = message ? string.Format.apply(this, [message].concat(Array.prototype.slice.call(arguments,3))) : "";
			msg += constraint.getMessage();
			throw new AssertionException(msg);
		}
	},
	//Equality Asserts
	AreEqual: function ( expected, actual, message ) {
		var args = [actual, Is.EqualTo(expected), message];
		this.That.apply(this, args.concat(Array.prototype.slice.call(arguments,3)));
	},
	AreNotEqual: function ( expected, actual, message ) {
		var args = [actual, Is.Not().EqualTo(expected), message];
		this.That.apply(this, args.concat(Array.prototype.slice.call(arguments,3)));
	},
	//Condition Asserts
	IsTrue: function ( actual, message ) {
		var args = [actual, Is.True(), message];
		this.That.apply(this, args.concat(Array.prototype.slice.call(arguments,2)));
	},
	IsFalse: function ( actual, message ) {
		var args = [actual, Is.False(), message];
		this.That.apply(this, args.concat(Array.prototype.slice.call(arguments,2)));
	},
	IsNull: function ( actual, message ) {
		var args = [actual, Is.Null(), message];
		this.That.apply(this, args.concat(Array.prototype.slice.call(arguments,2)));
	},
	IsNotNull: function ( actual, message ) {
		var args = [actual, Is.Not.Null(), message];
		this.That.apply(this, args.concat(Array.prototype.slice.call(arguments,2)));
	},
	IsNaN: function ( actual, message ) {
		var args = [actual, Is.Not.Number(), message];
		this.That.apply(this, args.concat(Array.prototype.slice.call(arguments,2)));
	},
	IsEmpty: function ( actual, message ) {
		var args = [actual, Is.Empty(), message];
		this.That.apply(this, args.concat(Array.prototype.slice.call(arguments,2)));
	},
	IsNotEmpty: function ( actual, message ) {
		var args = [actual, Is.Not.Empty(), message];
		this.That.apply(this, args.concat(Array.prototype.slice.call(arguments,2)));
	},
	//Exception Asserts
	Throws: function ( method, message ) {
		try {
			method();
		}
		catch(e) {
			return;
		}		
		var msg = message ? string.Format.apply(this, [message].concat(Array.prototype.slice.call(arguments,3))) : "";
		msg += constraint.getMessage();
		throw new AssertionException(msg);
	},
	//Text Asserts
	Contains: function ( actual, substring, message ) {
		var args = [actual, Text.Contains(substring), message];
		this.That.apply(this, args.concat(Array.prototype.slice.call(arguments,3)));
	}
}

//Synonyms
Assert.True = Assert.IsTrue;
Assert.False = Assert.IsFalse;
Assert.Null = Assert.IsNull;
Assert.NotNull = Assert.IsNotNull;